package com.miya.demo.service.impl;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.google.common.collect.Lists;
import com.miya.demo.common.constant.WechatConstant;
import com.miya.demo.common.enums.TemplateMsgTypeEnum;
import com.miya.demo.common.exception.BusinessException;
import com.miya.demo.common.util.*;
import com.miya.demo.model.dto.wechat.TemplateMsgDTO;
import com.miya.demo.model.dto.wechat.WechatAuthDTO;
import com.miya.demo.model.dto.wechat.WechatAuthDecryptDTO;
import com.miya.demo.model.vo.wechat.WeChatAccessTokenVO;
import com.miya.demo.model.vo.wechat.WeChatAuthVO;
import com.miya.demo.model.vo.wechat.WeChatSubscribedUserVO;
import com.miya.demo.model.vo.wechat.WeChatUserInfoVO;
import com.miya.demo.service.WechatService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 微信服务impl
 *
 * @author CaiXiaowei
 * @date 2022/08/31
 */
@Slf4j
@Service
public class WechatServiceImpl implements WechatService {

    @Value("${wechat.mini-pro.appid:''}")
    private String wechatMiniProAppId;

    @Value("${wechat.mini-pro.secret:''}")
    private String wechatMiniProSecret;

    /**
     * 微信公众号appId
     */
    @Value("${wechat.public.appid:''}")
    private String wechatPublicAppid;

    /**
     * 微信公众号appSecret
     */
    @Value("${wechat.public.secret:''}")
    private String wechatPublicSecret;

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 微信小程序授权
     *
     * @param wechatAuthDTO 微信auth dto
     * @return {@link WeChatAuthVO}
     */
    @Override
    public WeChatAuthVO authMiniPro(WechatAuthDTO wechatAuthDTO) {
        Map<String, Object> paramMap = new HashMap<>(16);
        String code = wechatAuthDTO.getCode();
        paramMap.put("grant_type", "authorization_code");
        paramMap.put("appid", wechatMiniProAppId);
        paramMap.put("secret", wechatMiniProSecret);
        paramMap.put("js_code", code);
        WeChatAuthVO weChatAuthVO = OkHttpUtil.builder()
                .url(WechatConstant.WECHAT_MINIPRO_AUTH_URL)
                .get(paramMap)
                .sync(WeChatAuthVO.class);
        redisUtil.set(WechatConstant.WECHAT_MINIPRO_AUTH_KEY + code, JSONUtil.toJsonStr(weChatAuthVO), 10, TimeUnit.MINUTES);
        return weChatAuthVO;
    }

    /**
     * 解密微信小程序授权手机号
     *
     * @param wechatAuthDecryptDTO 微信auth解密dto
     * @return {@link String}
     */
    @Override
    public String decryptMiniProPhoneNumber(WechatAuthDecryptDTO wechatAuthDecryptDTO) {
        String code = wechatAuthDecryptDTO.getCode();
        Object o = redisUtil.get(WechatConstant.WECHAT_MINIPRO_AUTH_KEY + code);
        WeChatAuthVO weChatAuthVO = null;
        if (o == null) {
            weChatAuthVO = this.authMiniPro(wechatAuthDecryptDTO);
        } else {
            weChatAuthVO = JSONUtil.toBean(Convert.toStr(o), WeChatAuthVO.class);
        }
        Assert.notNull(weChatAuthVO, "小程序授权失败，请稍后重试。");
        String sessionKey = weChatAuthVO.getSessionKey();
        String phoneNumber = WechatUtils.decryptData(wechatAuthDecryptDTO.getEncryptedData(), sessionKey, wechatAuthDecryptDTO.getIv());
        Assert.notEmpty(phoneNumber, "小程序手机号码授权失败，请稍后再试。");
        return phoneNumber;
    }

    /**
     * 获取微信访问令牌
     *
     * @param appid  appid
     * @param secret 秘密
     * @return {@link WeChatAccessTokenVO}
     */
    @Override
    public WeChatAccessTokenVO getWechatAccessToken(String appid, String secret) {
        Object o = redisUtil.get(WechatConstant.WECHAT_ACCESS_TOKEN_KEY);
        if (o != null) {
            WeChatAccessTokenVO weChatAccessTokenVO = JSONUtil.toBean(Convert.toStr(o), WeChatAccessTokenVO.class);
            if (weChatAccessTokenVO != null) {
                return weChatAccessTokenVO;
            }
        }
        Map<String, Object> paramMap = new HashMap<>(4);
        paramMap.put("grant_type", "client_credential");
        paramMap.put("appid", appid);
        paramMap.put("secret", secret);
        WeChatAccessTokenVO response = OkHttpUtil.builder()
                .url(WechatConstant.WECHAT_FETCH_ACCESS_TOKEN_URL)
                .get(paramMap)
                .sync(WeChatAccessTokenVO.class);
        log.info("调用微信刷新accessToken接口response=>{}", JSONUtil.toJsonStr(response));
        if (response.getErrcode() == null) {
            redisUtil.set(WechatConstant.WECHAT_ACCESS_TOKEN_KEY, JSONUtil.toJsonStr(response), 2, TimeUnit.HOURS);
            return response;
        } else {
            throw new BusinessException(response.getErrmsg());
        }
    }

    /**
     * 获取公众号关注用户
     *
     * @param openid 第一个拉取的OPENID，不填默认从头开始拉取
     * @return {@link List}<{@link String}>
     */
    @Override
    public List<String> getWechatUser(String openid) {
        WeChatAccessTokenVO wechatAccessToken = this.getWechatAccessToken(wechatPublicAppid, wechatPublicSecret);
        String nextOpenid = openid;
        List<String> openids = Lists.newArrayList();
        while (true) {
            Map<String, Object> paramMap = MapUtil.newHashMap();
            paramMap.put("access_token", wechatAccessToken.getAccessToken());
            if (StrUtil.isNotEmpty(nextOpenid)) {
                paramMap.put("next_openid", nextOpenid);
            }
            WeChatSubscribedUserVO response = OkHttpUtil.builder()
                    .url(WechatConstant.WECHAT_GET_USERS_URL)
                    .get(paramMap)
                    .sync(WeChatSubscribedUserVO.class);
            Assert.notNull(response, "调用微信获取已关注用户列表接口异常");
            openids.addAll(response.getData().getOpenid());
            if (StrUtil.isEmpty(response.getNextOpenId())) {
                break;
            }
            nextOpenid = response.getNextOpenId();
        }
        return openids;
    }

    /**
     * 获取公众号关注用户详情
     *
     * @param openid openid
     * @return {@link WeChatUserInfoVO}
     */
    @Override
    public WeChatUserInfoVO getWechatUserInfo(String openid) {
        WeChatAccessTokenVO wechatAccessToken = this.getWechatAccessToken(wechatPublicAppid, wechatPublicSecret);
        Map<String, Object> paramMap = new HashMap<>(8);
        paramMap.put("lang", "zh_CN");
        paramMap.put("openid", openid);
        paramMap.put("access_token", wechatAccessToken.getAccessToken());
        return OkHttpUtil.builder()
                .url(WechatConstant.WECHAT_OFFICIAL_NO_USERINFO_URL)
                .get(paramMap)
                .sync(WeChatUserInfoVO.class);
    }

    /**
     * 发送模板消息
     *
     * @param templateMsgDTO 模板消息dto
     * @return {@link Boolean}
     */
    @Override
    public Boolean sendTemplateMsg(TemplateMsgDTO templateMsgDTO) {
        Integer type = templateMsgDTO.getType();

        if (TemplateMsgTypeEnum.APPLETS.is(type)) {
            // 发送小程序模板消息
            WeChatAccessTokenVO wechatAccessToken = this.getWechatAccessToken(wechatMiniProAppId, wechatMiniProSecret);
            String accessToken = wechatAccessToken.getAccessToken();
            String templateContentParams = WeChatTemplateUtils.getSendMiniAppTemplateContentParams(templateMsgDTO);
            String responseContent = HttpUtil.post(WechatConstant.WECHAT_MINI_APP_TEMPLATE_MESSAGE_URL + accessToken, templateContentParams);
            return messageResult(responseContent);
        }
        if (TemplateMsgTypeEnum.PUBLIC.is(type)) {
            // 发送公众号模板消息
            WeChatAccessTokenVO wechatAccessToken = this.getWechatAccessToken(wechatPublicAppid, wechatPublicSecret);
            String accessToken = wechatAccessToken.getAccessToken();
            String templateContentParams = WeChatTemplateUtils.getSendPublicNoTemplateContentParams(templateMsgDTO);
            String responseContent = HttpUtil.post(WechatConstant.WECHAT_SEND_PUBLIC_TEMPLATE_MESSAGE_URL + accessToken, templateContentParams);
            return messageResult(responseContent);
        }

        return Boolean.FALSE;
    }

    private Boolean messageResult(String responseContent) {
        log.info("发送模板消息返回结果:{}", responseContent);
        Map resultMap = JSONUtil.toBean(responseContent, Map.class);
        int errcode = MapUtils.getIntValue(resultMap, "errcode");
        String errmsg = MapUtils.getString(resultMap, "errmsg");
        if (errcode == 0) {
            return Boolean.TRUE;
        }
        throw new BusinessException(errcode, errmsg);
    }
}
